package cc.shacocloud.mirage.utils.date.format;

import cc.shacocloud.mirage.utils.Tuple;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.text.DateFormat;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.Objects;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 日期格式化器缓存
 *
 * @author 思追(shaco)
 */
abstract class FormatCache<F extends Format> {
    
    private static final ConcurrentMap<Tuple, String> C_DATE_TIME_INSTANCE_CACHE = new ConcurrentHashMap<>(7);
    private final ConcurrentMap<Tuple, F> cInstanceCache = new ConcurrentHashMap<>(7);
    
    /**
     * 获取指定样式和区域设置的日期/时间格式
     *
     * @param dateStyle {@link FastDateFormatStyleEnum#getValue()}
     * @param timeStyle {@link FastDateFormatStyleEnum#getValue()}
     * @param locale    可选区域设置，覆盖系统区域设置
     * @return 本地化的标准日期时间格式化程序
     * @throws IllegalArgumentException 如果区域设置未定义日期时间模式
     */
    static String getPatternForStyle(final Integer dateStyle, final Integer timeStyle, final Locale locale) {
        final Tuple key = new Tuple(dateStyle, timeStyle, locale);
        
        String pattern = C_DATE_TIME_INSTANCE_CACHE.get(key);
        if (pattern == null) {
            try {
                DateFormat formatter;
                if (dateStyle == null) {
                    formatter = DateFormat.getTimeInstance(timeStyle, locale);
                } else if (timeStyle == null) {
                    formatter = DateFormat.getDateInstance(dateStyle, locale);
                } else {
                    formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
                }
                pattern = ((SimpleDateFormat) formatter).toPattern();
                final String previous = C_DATE_TIME_INSTANCE_CACHE.putIfAbsent(key, pattern);
                if (previous != null) {
                    // 即使另一个线程是否放置该模式并不重要，但返回实际位于 ConcurrentMap 中的字符串实例仍然是一个好做法
                    pattern = previous;
                }
            } catch (final ClassCastException ex) {
                throw new IllegalArgumentException("没有区域设置的日期时间模式：" + locale);
            }
        }
        return pattern;
    }
    
    /**
     * 使用默认的pattern、timezone和locale获得缓存中的实例
     */
    public F getInstance() {
        return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, null, null);
    }
    
    /**
     * 使用 pattern, time zone and locale 获得对应的 格式化器
     *
     * @param pattern  非空日期格式，使用与 {@link java.text.SimpleDateFormat}相同格式
     * @param timeZone 时区，默认当前时区
     * @param locale   地区，默认使用当前地区
     * @return 格式化器
     * @throws IllegalArgumentException pattern 无效或{@code null}
     */
    public F getInstance(@NotNull String pattern, TimeZone timeZone, Locale locale) {
        if (timeZone == null) {
            timeZone = TimeZone.getDefault();
        }
        if (locale == null) {
            locale = Locale.getDefault();
        }
        final Tuple key = new Tuple(pattern, timeZone, locale);
        F format = cInstanceCache.get(key);
        if (format == null) {
            format = createInstance(pattern, timeZone, locale);
            final F previousValue = cInstanceCache.putIfAbsent(key, format);
            if (previousValue != null) {
                // 另一个线程偷偷溜进来并做了同样的工作，我们应该返回ConcurrentMap中的实例
                format = previousValue;
            }
        }
        return format;
    }
    
    /**
     * 创建格式化器
     *
     * @param pattern  非空日期格式，使用与 {@link java.text.SimpleDateFormat}相同格式
     * @param timeZone 时区，默认当前时区
     * @param locale   地区，默认使用当前地区
     * @return 格式化器
     * @throws IllegalArgumentException pattern 无效或{@code null}
     */
    abstract protected F createInstance(String pattern, TimeZone timeZone, Locale locale);
    
    /**
     * 获取使用指定样式、时区和区域设置的日期时间格式化程序实例。
     *
     * @param dateStyle {@link FastDateFormatStyleEnum#getValue()}
     * @param timeStyle {@link FastDateFormatStyleEnum#getValue()}
     * @param timeZone  可选时区，覆盖格式化日期的时区，null 表示使用默认区域设置
     * @param locale    可选区域设置，覆盖系统区域设置
     * @return 本地化的标准日期时间格式化程序
     * @throws IllegalArgumentException 如果区域设置未定义日期时间模式
     */
    private F getDateTimeInstance(@Nullable Integer dateStyle,
                                  @Nullable Integer timeStyle,
                                  @Nullable TimeZone timeZone,
                                  @Nullable Locale locale) {
        if (locale == null) {
            locale = Locale.getDefault();
        }
        final String pattern = getPatternForStyle(dateStyle, timeStyle, locale);
        return getInstance(pattern, timeZone, locale);
    }
    
    /**
     * 使用指定的样式、时区和区域设置获取日期/时间格式化程序实例。
     *
     * @param dateStyle {@link FastDateFormatStyleEnum#getValue()}
     * @param timeStyle {@link FastDateFormatStyleEnum#getValue()}
     * @param timeZone  可选时区，覆盖格式化日期的时区，null 表示使用默认区域设置
     * @param locale    可选区域设置，覆盖系统区域设置
     * @return 本地化的标准日期时间格式化程序
     * @throws IllegalArgumentException 如果区域设置未定义日期时间模式
     */
    F getDateTimeInstance(@Nullable FastDateFormatStyleEnum dateStyle,
                          @Nullable FastDateFormatStyleEnum timeStyle,
                          @Nullable TimeZone timeZone,
                          @Nullable Locale locale) {
        return getDateTimeInstance(Objects.isNull(dateStyle) ? null : dateStyle.getValue(),
                Objects.isNull(timeStyle) ? null : timeStyle.getValue(),
                timeZone,
                locale);
    }
    
    /**
     * 使用指定的样式、时区和区域设置获取日期格式化程序实例。
     *
     * @param dateStyle {@link FastDateFormatStyleEnum}
     * @param timeZone  可选时区，覆盖格式化日期的时区，null 表示使用默认区域设置
     * @param locale    可选区域设置，覆盖系统区域设置
     * @return 本地化的标准日期时间格式化程序
     * @throws IllegalArgumentException 如果区域设置未定义日期时间模式
     */
    F getDateInstance(FastDateFormatStyleEnum dateStyle, final TimeZone timeZone, final Locale locale) {
        return getDateTimeInstance(dateStyle, null, timeZone, locale);
    }
    
    /**
     * 使用指定的样式、时区和区域设置获取日期格式化程序实例。
     *
     * @param timeStyle {@link FastDateFormatStyleEnum }
     * @param timeZone  可选时区，覆盖格式化日期的时区，null 表示使用默认区域设置
     * @param locale    可选区域设置，覆盖系统区域设置
     * @return 本地化的标准日期时间格式化程序
     * @throws IllegalArgumentException 如果区域设置未定义日期时间模式
     */
    F getTimeInstance(FastDateFormatStyleEnum timeStyle, final TimeZone timeZone, final Locale locale) {
        return getDateTimeInstance(null, timeStyle, timeZone, locale);
    }
}
